package com.airpi.connectivity;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Scanner;

import android.app.FragmentManager;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.DhcpInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.text.format.Formatter;
import android.util.Log;

import com.airpi.R;

/**
 * Class that manages to discover the Raspberry Pi on the LAN
 * @author Romain Pellerin
 */
public class RaspberryPiDiscoverer extends AsyncTask<Void, Integer, Integer> {

	// Preferences
	public final static String PREFS_USER_PI_AUTO	= "pref_rpi_autodetect";
	public final static String PREFS_USER_IP		= "pref_rpi_ip";
	public final static String PREFS_LAST_FOUND		= "last_rpi_ip";
	// Others
	private final static String SEARCH_DIALOG	= "search_dialog";	// Tag of the Dialog
	private final static int timeout			= 250;				// Timeout for ping

	// Private fields
	private Context context;
	private SearchingRaspberryDialogFragment dialog;
	private SharedPreferences sharedPrefs;
	private FragmentManager fm;
	private boolean isCancelled;
	private int max = 0;

	/**
	 * Constructor
	 * @param context The context that created this instance
	 */
	public RaspberryPiDiscoverer(Context context, FragmentManager fm) {
		this.context		= context;
		this.sharedPrefs	= PreferenceManager.getDefaultSharedPreferences(this.context);
		this.fm				= fm;
		this.isCancelled	= false;
	}

	public void setIsCancelled(boolean b) {
		this.isCancelled = b;
	}
	
	public boolean getIsCancelled() {
		return this.isCancelled;
	}
	
	public int getMax() {
		return this.max;
	}
	
	@Override
	protected void onPreExecute() {
		if (!getIsCancelled()) {
			SearchingRaspberryDialogFragment.attach(this);
			this.dialog = new SearchingRaspberryDialogFragment();
			this.dialog.show(fm, RaspberryPiDiscoverer.SEARCH_DIALOG);
		}
	}
	
	@Override
	protected Integer doInBackground(Void... params) {
		while(this.dialog.getDialog()==null) {} // bug fix
		
		if (getIsCancelled()) return R.string.login_searching_fail;

		if (sharedPrefs.getBoolean(PREFS_USER_PI_AUTO, true)) {							// AUTO SEARCH
			WifiManager wifiMgr = (WifiManager) this.context.getSystemService(Context.WIFI_SERVICE);
			if (!wifiMgr.isWifiEnabled()) {											// WIFI DISABLED
				return R.string.login_searching_no_wifi;
			}
			else {																	// WIFI ENABLED
				WifiInfo wifiinfo = wifiMgr.getConnectionInfo();
				int ip;
				if (wifiinfo == null || (ip = wifiinfo.getIpAddress()) == 0) {	// NOT CONNECTED
					return R.string.login_searching_no_wifi;
				}
				else {															// CONNECTED
					String last_found_ip = sharedPrefs.getString(PREFS_LAST_FOUND, null);
					try {
						if (last_found_ip != null && checkIp(last_found_ip))
							return R.string.login_searching_success;
					} catch (Exception e1) {/*ignore*/};

					DhcpInfo d = wifiMgr.getDhcpInfo();

					int ip_device	= ip;
					int netmask		= d.netmask;	

					int ip_network		= (ip_device & netmask);
					int ip_broadcast	= (ip_device & netmask) | ~netmask;

					Log.e("network",String.valueOf(ip_network));
					Log.e("network",Formatter.formatIpAddress(ip_network).toString());
					Log.e("broadcast",String.valueOf(ip_broadcast));
					
					Log.e("broadcast",Formatter.formatIpAddress(ip_broadcast).toString());
					
					int dialog_counter	= 0;
					int dialog_total	= 0;
					
					for (int i=ip_network+0x01000000;i!=ip_broadcast;i+=0x01000000){
						dialog_total++;
					}
					this.max = dialog_total;
					this.dialog.getDialog().setMax(this.max);
					
					for (int i=ip_network+0x01000000;i!=ip_broadcast;i+=0x01000000){
						if (getIsCancelled()) {
							return R.string.login_searching_aborted;
						}
						dialog_counter++;
						this.publishProgress(dialog_counter);

						String ip_tested=Formatter.formatIpAddress(i);
						try {
							if (this.checkIp(ip_tested)) {
								sharedPrefs.edit().putString(PREFS_LAST_FOUND, ip_tested).commit();
								return R.string.login_searching_success;
							}
						}
						catch(Exception e) {
							break;
						}
					}
					return R.string.login_searching_fail; // PROBLEM
				}
			}
		}
		else {																			// MANUAL SEARCH
			this.max = 1;
			this.dialog.getDialog().setMax(this.max);
			this.publishProgress(1);

			String ip_of_user = sharedPrefs.getString(PREFS_USER_IP, null);
			if (ip_of_user == null) {												// NO IP HAS BEEN GIVEN
				return R.string.login_searching_no_valid_ip_given;
			}
			else {																	// TRY THE GIVEN IP
				try {
					boolean res = checkIp(ip_of_user);
					if (res) {
						sharedPrefs.edit().putString(PREFS_LAST_FOUND, ip_of_user).commit();
						return R.string.login_searching_success;
					}
					else
						return R.string.login_searching_fail;
				} catch (Exception e) {
					return R.string.login_searching_no_valid_ip_given;
				}
			}
		}
	}

	@Override
	protected void onProgressUpdate(Integer... progress) {
		if (this.dialog.getDialog()!=null)
			this.dialog.getDialog().setProgress(progress[0]);
		else
			this.cancel(false);
	}

	@Override
	protected void onPostExecute(Integer result) {
		if (this.dialog != null && !this.isCancelled)
			this.dialog.dismiss(result);
	}
	
	/**
	 * Check if the IP is the Raspberry Pi IP
	 * @param ip The IP to check
	 * @return True if it's the Raspberry Pi, False otherwise
	 * @throws IOException Problem
	 * @throws UnknownHostException Problem
	 */
	private boolean checkIp(String host) throws UnknownHostException, IOException {
		Log.v("test ip", "testing: "+host);
		if (InetAddress.getByName(host).isReachable(timeout)){
			Log.e("test ip",host + " is reachable");
			if (downloadUrl("http://"+host+":"+sharedPrefs.getString("pref_rpi_port", "8080")+"/airpi/airpi.html").contains("AirPi")) {
				Log.e("test ip","IP FOUND");
				return true;
			}
		}
		return false;
	}

	/**
	 * Download the content of a web page
	 * @param url The URL to target
	 * @return The content of the web page, or "-1" if there is an error
	 */
	private String downloadUrl(String url) {
		InputStream is = null;
		try {
			URL murl = new URL(url);
			HttpURLConnection conn = (HttpURLConnection) murl.openConnection();
			conn.setConnectTimeout(2000); // milliseconds
			conn.setReadTimeout(2000); // milliseconds
			conn.setRequestMethod("GET");
			conn.connect();
			if (conn.getResponseCode() != 200) throw new Exception();
			is = conn.getInputStream();
			String contentAsString = isToString(is);
			return contentAsString;
		}
		catch (Exception e) {
			return "-1";
		}
		finally {
			try {if (is != null) is.close();} catch (IOException e){/*ignore*/}
		}
	}

	/**
	 * Converts an {@link InputStream} to a {@link String}
	 * @param is The InputStream to convert
	 * @return The String obtained from the InputStream
	 */
	private String isToString(InputStream is) {
		Scanner s = null;
		try {
			s = new Scanner(is, "UTF-8").useDelimiter("\\A");
			return s.hasNext() ? s.next() : "";
		}
		finally {
			if (s!=null) s.close();
		}
	}

	public void attach(SearchingRaspberryDialogFragment searchingRaspberryDialogFragment) {
		this.dialog = searchingRaspberryDialogFragment;
	}	
}
